# Import packages
library(tidyverse)
library(lubridate)
library(RQuantLib)
library(plotly)

Step 0: Import data and reference indices; remove superfluous variables to create datasets for agency correction

# Import needed indices
setwd('Datasets/Indices')
The working directory was changed to N:/CommDev/Research/Research/Parks_Regional/Use Estimates/2018/Use Estimate Creation/Datasets/Indices inside a notebook chunk. The working directory will be reset when the chunk is finished running. Use the knitr root.dir option in the setup chunk to change the working directory for notebook chunks.
park_ids <- read_csv('Park and Agency Codes Index.csv') # For Step 2
Parsed with column specification:
cols(
  Park_id = col_integer(),
  Park_name = col_character(),
  Agency_name = col_character(),
  Agency_id = col_integer()
)
entrance_classifications <- read_csv('2018 Entrance Usage Classification Index with corrections.csv') # For Step 3
Parsed with column specification:
cols(
  Park_trail = col_integer(),
  Entrance = col_integer(),
  Usage_class = col_character(),
  Start_time = col_integer(),
  Day_type = col_character()
)
bus_vehicle_multipliers <- read_csv('Bus and Vehicle Multipliers Index.csv') # For Step 4
Parsed with column specification:
cols(
  Park = col_integer(),
  ppb_multiplier = col_double(),
  ppv_multiplier = col_double()
)
park_agencies_multipliers <- read_csv('2018 Entrance Usage Classifications with Agency Attribution Index.csv') # For Step 3 & 5
Parsed with column specification:
cols(
  Agency_name = col_character(),
  Agency_id = col_integer(),
  Park_name = col_character(),
  Park_trail_id = col_integer(),
  Entrance_id = col_integer(),
  Start_time = col_time(format = ""),
  Day_type = col_character(),
  Usage_class = col_character()
)

Run the following import and export once, prior to requesting Qualtrics accuracy audits at the end of the season

# Import 2018 Qualtrics datasets - see Qualtrics Info document for login information
# Note that for import by code below to work, all datasets must be csvs, saved with original Qualtrics names in 'Datasets' folder (i.e. download as csv from Qualtrics and do not change the name)
setwd('.')
setwd('Datasets/Qualtrics')
The working directory was changed to N:/CommDev/Research/Research/Parks_Regional/Use Estimates/2018/Use Estimate Creation/Datasets/Qualtrics inside a notebook chunk. The working directory will be reset when the chunk is finished running. Use the knitr root.dir option in the setup chunk to change the working directory for notebook chunks.
parks_18 <- list.files(pattern = '*.csv')
make_names <- function(name_vector) {
 
  name_vector_df <- as.data.frame(name_vector)
 
 new_names <- name_vector_df %>%
   rename(Agency_name = 1) %>%
   mutate(Agency_name = str_replace_all(Agency_name, c('\\+-+.+?v' = '', '\\+' = '_')))
 new_names_list <- as.list(new_names)
 
 return(new_names_list$Agency_name)
}
list2env(map(set_names(parks_18, make_names(parks_18)), read_csv), envir = .GlobalEnv)
Duplicated column names deduplicated: 'Q18' => 'Q18_1' [28], 'Q22' => 'Q22_1' [29], 'Q26' => 'Q26_1' [32], 'Q27' => 'Q27_1' [33]Parsed with column specification:
cols(
  .default = col_character()
)
See spec(...) for full column specifications.
Duplicated column names deduplicated: 'Q28' => 'Q28_1' [27], 'Q26' => 'Q26_1' [32], 'Q27' => 'Q27_1' [33]Parsed with column specification:
cols(
  .default = col_character()
)
See spec(...) for full column specifications.
Duplicated column names deduplicated: 'Q17' => 'Q17_1' [22], 'Q21' => 'Q21_1' [28], 'Q26' => 'Q26_1' [32], 'Q27' => 'Q27_1' [33]Parsed with column specification:
cols(
  .default = col_character()
)
See spec(...) for full column specifications.
Duplicated column names deduplicated: 'Q17' => 'Q17_1' [22], 'Q26' => 'Q26_1' [32], 'Q27' => 'Q27_1' [33]Parsed with column specification:
cols(
  .default = col_character()
)
See spec(...) for full column specifications.
Duplicated column names deduplicated: 'Q17' => 'Q17_1' [22], 'Q26' => 'Q26_1' [32], 'Q27' => 'Q27_1' [33]Parsed with column specification:
cols(
  .default = col_character()
)
See spec(...) for full column specifications.
Duplicated column names deduplicated: 'Q17' => 'Q17_1' [22], 'Q26' => 'Q26_1' [32], 'Q27' => 'Q27_1' [33]Parsed with column specification:
cols(
  .default = col_character()
)
See spec(...) for full column specifications.
Duplicated column names deduplicated: 'Q17' => 'Q17_1' [22], 'Q26' => 'Q26_1' [32], 'Q27' => 'Q27_1' [33]Parsed with column specification:
cols(
  .default = col_character()
)
See spec(...) for full column specifications.
Duplicated column names deduplicated: 'Q17' => 'Q17_1' [22], 'Q26' => 'Q26_1' [32], 'Q27' => 'Q27_1' [33]Parsed with column specification:
cols(
  .default = col_character()
)
See spec(...) for full column specifications.
Duplicated column names deduplicated: 'Q17' => 'Q17_1' [22], 'Q26' => 'Q26_1' [32], 'Q27' => 'Q27_1' [33]Parsed with column specification:
cols(
  .default = col_character()
)
See spec(...) for full column specifications.
Duplicated column names deduplicated: 'Q17' => 'Q17_1' [22], 'Q21' => 'Q21_1' [28], 'Q26' => 'Q26_1' [32], 'Q27' => 'Q27_1' [33]Parsed with column specification:
cols(
  .default = col_character()
)
See spec(...) for full column specifications.
<environment: R_GlobalEnv>
all_agencies_list <- list(Anoka_County = Anoka_County, Bloomington = Bloomington, Carver_County = Carver_County, Dakota_County = Dakota_County, Minneapolis_Park_and_Recreation_Board = Minneapolis_Park_and_Recreation_Board, Ramsey_County = Ramsey_County, Saint_Paul = Saint_Paul, Scott_County = Scott_County, Three_Rivers_Park_District = Three_Rivers_Park_District, Washington_County = Washington_County)
# Remove unnecessary variables to clean datasets prior to sending to agencies
# 
# variable_paring <- function(df) {
#   df %>%
#     slice(-2) %>%
#     select(18:33)
# }
# 
# parks_pared <- map(all_agencies_list, variable_paring)
# Write out datasets without extra variables to folder on N drive, from which they can be sent for correction
# parks_pared %>%
#   names(.) %>%
#   map(~ write_csv(parks_pared[[.]], paste0("Datasets/To Send for Accuracy Audit/", ., ".csv")))

Step 1: Tidy agencies’ final data (with corrections), bind together

#setwd('.')
#setwd('Datasets/Audited')
#parks_18 <- list.files(pattern = '*.csv')
all_agencies_list <- list(Anoka_County, Bloomington, Carver_County, Dakota_County, Minneapolis_Park_and_Recreation_Board, Ramsey_County, Saint_Paul, Scott_County, Three_Rivers_Park_District, Washington_County)
park_tidy <- function(df) {
  df %>%
    slice(-1:-2) %>%
  rename(Counter_initials = 18,
         Agency_name = 19,
         Park_trail = 20,
         Entrance = 21,
         Month = 22,
         Day = 23,
         Year = 24,
         Timeslot = 25,
         Scheduled_count = 26,
         Scheduled_date = 27,
         Bikers = 28,
         Peds_skaters = 29,
         Vehicles = 30,
         Buses = 31,
         Horse_riders = 32,
         Boaters = 33) %>%
  select(8, 9, 18:33) %>%
  mutate(Start_time = ifelse(Timeslot != '10:00-12:00 PM', str_replace(Timeslot, '\\-.+? ', ''), '10:00AM')) %>%
  separate(Start_time, into = c('Start', 'AM_PM'), sep = -2) %>%
  unite(Start_time, Start, AM_PM, sep = ' ') %>%
  mutate(Zero = 0,
         Start_time_2 = Start_time) %>%
  unite(Start_alt, Zero, Start_time_2, sep = '') %>%
  mutate(Start_time_fmt = ifelse(Start_time != '10:00 AM' & Start_time != '12:00 PM', Start_alt, Start_time)) %>%
  select(-Start_alt)
  
}
agencies_tidied <- map(all_agencies_list, park_tidy)
agencies_bound <- do.call(bind_rows, agencies_tidied)

Step 2: Create a day type variable (weekend/holiday or weekday); create a variable with the total number of days for that day type in the season

Make sure, in this step, to change the year in the first two lines to the pertinent year - this has to be done manually because while usually use estimates are done the year after summer counts are complete, it could be that they are done in the same year as the counts in years to come

# Calculate days in the summer season for the year
memorial_labor <- as.data.frame(RQuantLib::getHolidayList(calendar = 'UnitedStates', from = as_date(ymd('20180501')), to = as_date(ymd('20180930')), includeWeekends = FALSE))
summer_days <- memorial_labor %>%
  rename(Date = 1) %>%
  slice(-2) %>% # remove Fourth of July
  mutate(row_id = row_number()) %>%
  mutate(Holiday = ifelse(row_id == 1, 'Memorial_Day', 'Labor_Day')) %>%
  select(-row_id) %>%
  spread(Holiday, value = Date) %>%
  mutate(Memorial_Day_Saturday = as_date(ymd(Memorial_Day))- days(2)) %>% # Summer season begins the Saturday before Memorial Day
  mutate(Season_period = as.period(interval(start = as_date(ymd(Memorial_Day_Saturday)), end = as_date(ymd(Labor_Day))), unit = 'day')) %>%
  separate(Season_period, into = c('Season_days', "HMS_in_season"), sep = 'd') %>%
  mutate(Half_of_summer = (as.numeric(Season_days)/2)) %>%
  mutate(Midpoint_of_summer = as_date(ymd(Memorial_Day_Saturday)) + days(Half_of_summer))
# Calculate number of weekend/holiday days and number of weekdays in summer season - this 
day_type_counts <- as.data.frame(seq(as.Date(summer_days$Memorial_Day_Saturday), as.Date(summer_days$Labor_Day), "days")) %>%
  rename(Date = 1) %>%
  mutate(Holiday = RQuantLib::isHoliday(calendar = "UnitedStates", dates = Date)) %>%
  mutate(Day_type = ifelse(Holiday == 'TRUE', 'Weekends_holidays', 'Weekdays')) %>%
  group_by(Day_type) %>%
  count() %>%
  spread(Day_type, value = n)
# Assign which half of the summer the day falls in (this is used, in part, ); assign day-type variable; assign number of days of that day-type in the season (this number is used as a multiplier to scale counts up)
step_II <- agencies_bound %>%
  mutate(Year = ifelse(is.na(Year), '2018', Year)) %>% # Some agencies don't enter the year; fill in for them
  mutate(Month_unite = Month,
         Day_unite = Day,
         Year_unite = Year) %>%
  unite(Date, Month_unite, Day_unite, Year_unite, sep = " ") %>%
  mutate(Date = as_date(mdy(Date))) %>%
  mutate(Half_of_summer = ifelse(Date <= summer_days$Midpoint_of_summer, 1, 2)) %>% 
  unite(Date_year, Month, Day, Year, sep = '-') %>%
  mutate(Date_object = as_date(mdy(Date_year))) %>%
  mutate(Holiday = RQuantLib::isHoliday(calendar = "UnitedStates", dates = Date_object)) %>%
  mutate(Day_type = ifelse(Holiday == 'TRUE', 'Weekend_holiday', 'Weekday')) %>%
  mutate(Total_days_by_type = ifelse(Day_type == 'Weekend_holiday', day_type_counts$Weekends_holidays, day_type_counts$Weekdays)) %>%
  select(-Holiday) %>%
  mutate(Park_trail = ifelse(Park_trail == 'Minnesota River Greenway', 'Minnesota River Greenway Regional Trail', Park_trail)) %>%
  mutate(Agency_name = ifelse(Park_trail == 'Battle Creek Regional Park', 'Ramsey County',
                              ifelse(Park_trail == 'Mississippi Gorge Regional Park', 'Minneapolis Park and Recreation Board',
                                     ifelse(Park_trail == 'Hidden Falls-Crosby Farm Regional Park', 'Saint Paul',
                                            ifelse(Park_trail == 'Scott County Regional Trail', 'Scott County', Agency_name))))) %>% # These agency selection for these trails from the Qualtrics form are incorrect
  mutate(Start_time = as.character(Start_time_fmt)) %>%
  select(-Start_time_fmt)

Step 2a: Add numeric Park Identifier to dataset (used in step 3 to add park entrance usage classifications)

step_IIa <- left_join(step_II, park_ids, by = c('Park_trail' = 'Park_name', 'Agency_name' = 'Agency_name'))
# Ensure all data joined properly
step_IIa_unjoined <- step_IIa %>%
  filter(is.na(Park_id) | is.na(Agency_id))

Step 2a check: Check the number of observations in the step_IIa dataset. If the number of observations increased, some of the counts have been duplicated upon join. Use the following code to discern why and how to ameliorate the faux pas.


# Which agency or agencies have duplicated counts?

# step_II_agency_counts <- step_II %>%
#   group_by(Agency_name) %>%
#   count()
# 
# step_IIa_agency_counts <- step_IIa %>%
#   group_by(Agency_name) %>%
#   count()
# 
# step_IIa_compare_agency_counts <- full_join(step_II_agency_counts, step_IIa_agency_counts, by = c('Agency_name'))
# 
# # In this case, only Anoka.  Which Anoka parks or trails were duplicated?
# 
# step_II_park_counts <- step_II %>%
#   filter(Agency_name == 'Anoka County') %>%
#   group_by(Park_trail) %>%
#   count()
# 
# step_IIa_park_counts <- step_IIa %>%
#   filter(Agency_name == 'Anoka County') %>%
#   group_by(Park_trail) %>%
#   count()
# 
# step_IIa_compare_park_counts <- full_join(step_II_park_counts, step_IIa_park_counts, by = c('Park_trail'))
# 
# step_IIa_compare_counts %>%
#   filter(n.x != n.y)
# # In this case, Rum River Central Regional Park is responsible for the duplication.  Rum River CRP was given two park attributions for Anoka County, by mistake.  Remove the '109' attribution from the index.
# 
# step_IIa_unjoined <- step_IIa %>%
#   filter(is.na(Park_id) | is.na(Agency_id)) %>%
#   select(Park_trail, Agency_name) %>%
#   unique()

Step 3: Add Park Entrance Usage Classifications (High, Medium, Low)

# Note that Trout Brook's RT Entrance 4 was fixed manually; if the 2018 Entrance Usage Classifications Index is re-run in the conversion workbook, this will again need to be changed manually (or scripted) according to the document 'Trout Brook RT Entrnace 4 2018 Designations'
entrance_class_join <- entrance_classifications %>%
  mutate(Entrance = as.character(Entrance)) %>%
  mutate(Start_time = format(strptime(substr(as.POSIXct(sprintf("%04.f", as.numeric(Start_time)), format="%H%M"), 12, 16), '%H:%M'), '%I:%M %p'))
step_III <- left_join(step_IIa, entrance_class_join, by = c('Park_id' = 'Park_trail', 'Entrance', 'Start_time', 'Day_type'))
entrance_class_join %>%
  filter(Park_trail == 61 & Entrance == 1)

Step 4: Add persons per vehicle and persons per bus multipliers, defined by the 2016 Visitor Study and 1998-1999 Visitor Study, respectively

step_IV <- left_join(step_III, bus_vehicle_multipliers, by = c('Park_id' = 'Park'))

Code below checks which parks did not have multiplier

step_IV_unjoined <- step_IV %>%
   filter(is.na(ppb_multiplier) | is.na(ppv_multiplier)) %>%
   select(Park_id) %>%
   unique()
## These parks are new and therefore don't have assigned multipliers; however they have zero vehicles or buses entering the park
# Check full number of observations missing data
step_IV_unjoined_full <- step_IV %>%
  filter(is.na(ppb_multiplier) | is.na(ppv_multiplier))
# Check number of buses and vehicles that came into the new parks
step_IV_unjoined_bv <- step_IV %>%
  filter(Park_id == 133 | Park_id == 140) %>%
  select(Vehicles, Buses) %>%
  filter(Vehicles != 0 | Buses != 0)
## Change missing ppv and ppb multipliers to 1
step_IV_tidy <- step_IV %>%
  mutate(ppb_multiplier = ifelse(is.na(ppb_multiplier), 1, ppb_multiplier),
         ppv_multiplier = ifelse(is.na(ppv_multiplier), 1, ppv_multiplier))

Step 5: Calculate expansion factors & attribute to parks

expansion_factors <- entrance_classifications %>%
  group_by(Park_trail, Usage_class, Day_type) %>%
  count() %>%
  rename(Expansion_factor = n)
# Join multipliers to parks to use to calculate total number of visitors in sample
step_V <- left_join(step_IV_tidy, expansion_factors, by = c('Park_id' = 'Park_trail', 'Usage_class' = 'Usage_class', 'Day_type' = 'Day_type'))
# Check that observations are not missing an expansion factor assignment.  No observation should be missing an assignment (i.e. this code should return a tibble with 0 observations).  If observations are missing an assignment, follow the same steps outlined in the missing assignment check of Step 3 of this script.
step_V_unjoined <- step_V %>%
  filter(is.na(Expansion_factor))
# For purposes of preliminary check of numbers, use only those counts that have an expansion factor assigned (un-assigned factors may be because parks are new or because of incorrect data entry)
step_V_not_na <- step_V %>%
  filter(!is.na(Expansion_factor))
expansion_factors %>%
  filter(Park_trail_id == 114)
Error in filter_impl(.data, quo) : 
  Evaluation error: object 'Park_trail_id' not found.

Step 6: Compute expanded total of visitors (total for entire season, based on sample) at entrance and by date and time of day

# Compute expanded total of visitors (total for entire season, based on sample)
step_VI <- step_V_not_na %>%
  mutate(Expanded_total = (ppv_multiplier*as.numeric(Vehicles) + as.numeric(Bikers) + as.numeric(Peds_skaters) + ppb_multiplier*as.numeric(Buses) + as.numeric(Horse_riders) + as.numeric(Boaters)) * as.numeric(Total_days_by_type) * as.numeric(Expansion_factor))

Step 7: Add to previous three years of data

x2015_2017 <- x2014_2017 %>%
  mutate(Least_recent_year = min(Year)) %>%
  filter(Year != Least_recent_year) %>% # Filter down to three most recent years of data
  select(-Least_recent_year) %>%
  separate(gooddate, into = c('Month', 'Day'), sep = 1) %>%
  mutate(Year_for_date = Year) %>%
  unite(Date, Month, Day, Year_for_date, sep = '-') %>%
  mutate(Date = as_date(mdy(Date))) %>%
  rename(Agency_id = agency,
         Park_id = Park,
         Bikers = Biker,
         Peds_skaters = Pedskat,
         Boaters = Boat,
         Vehicles = Vehicle,
         Buses = Bus,
         Horse_riders = Horse,
         Usage_class = samclass,
         Total_visitors = totalall,
         Day_type = daytype,
         Start_time = orgstart,
         Expansion_factor = expfact,
         ppv_multiplier = new2008ppv,
         ppb_multiplier = newppb,
         Total_days_by_type = `day#s`,
         Half_of_summer = half,
         Expanded_total = totalsum) %>%
  select(-carpeop, -buspeop) %>%
  mutate(Start_time = str_replace(Start_time, "10000", "1000"),
         Start_time = as.numeric(Start_time)) %>%
  mutate(Usage_class = ifelse(Usage_class == 1, 'High',
                              ifelse(Usage_class == 2, 'Medium', 'Low'))) %>%
  mutate(Start_time = format(strptime(substr(as.POSIXct(sprintf("%04.f", as.numeric(Start_time)), format="%H%M"), 12, 16), '%H:%M'), '%I:%M %p')) %>%
  mutate(Day_type = ifelse(Day_type == 1, 'Weekend_holiday', 'Weekday'))
x2018 <- step_VI %>%
  mutate(Year = 2018) %>%
  mutate(Entrance = as.numeric(Entrance),
         Bikers = as.numeric(Bikers),
         Peds_skaters = as.numeric(Peds_skaters),
         Vehicles = as.numeric(Vehicles),
         Buses = as.numeric(Buses),
         Boaters = as.numeric(Boaters))
  
x2015_2018 <- bind_rows(x2015_2017, x2018)
Error in bind_rows_(x, .id) : 
  Column `Horse_riders` can't be converted from integer to character

Step 8: Compute averages by Park, Trail Entrance/Timeslot Classification, and Day type (Weekend/holiday or Weekday)

x2015_2018_avg <- x2015_2018 %>%
  mutate(Park_id = ifelse(Park_id == 121, 116, Park_id)) %>% # Not sure why, but in past years SW RT was coded as 121.  In all documentation, however, it's coded as 116
  unique() %>%
  group_by(Park_id, Usage_class, Day_type) %>%
  mutate(Average_expanded_total = mean(Expanded_total)) %>%
  ungroup() %>%
  select(Park_id, Usage_class, Day_type, Average_expanded_total) %>%
  unique() %>%
  select(-Usage_class, -Day_type) %>%
  group_by(Park_id) %>%
  mutate(Estimate_18 = sum(Average_expanded_total)) %>%
  select(-Average_expanded_total) %>%
  unique() %>%
  ungroup() %>%
  mutate(Park_id = as.numeric(Park_id)) %>%
  arrange(Park_id)

Step 8a: Compare to previous year’s estimates; note that large differences do not inherently mean incorrect estimates


Following is an exploration of outliers

# Plot 2018 counts as striplot; use plotly to mouse over specific observations that appear to be outliers
stripchart_18 <- x2018 %>%
  mutate(Total_visitors = (ppv_multiplier*as.numeric(Vehicles) + as.numeric(Bikers) + as.numeric(Peds_skaters) + ppb_multiplier*as.numeric(Buses) + as.numeric(Horse_riders) + as.numeric(Boaters))) %>%
  ggplot(aes(Park_id, Total_visitors, color = Agency_id)) +
  geom_point(alpha = 0.6, size = 2) +
  coord_flip() +
  scale_color_distiller(palette = "RdPu") +
  labs(title = '2018 Raw Counts',
       y = 'Visitors Counted') +
  theme(axis.text.y = element_text(size = 8))
ggplotly(stripchart_18)
# Plot 2018 counts ; use plotly to mouse over specific observations that appear to be outliers
boxplot_18 <- x2018 %>%
  mutate(Total_visitors = (ppv_multiplier*as.numeric(Vehicles) + as.numeric(Bikers) + as.numeric(Peds_skaters) + ppb_multiplier*as.numeric(Buses) + as.numeric(Horse_riders) + as.numeric(Boaters))) %>%
  ggplot(aes(Park_trail, Total_visitors)) +
  geom_boxplot(alpha = 0.6, size = .5) +
  coord_flip() +
  labs(title = '2018 ',
       y = 'Counted Visitors') +
  theme(axis.text.y = element_text(size = 8))
ggplotly(boxplot_18)

LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpgYGB7cn0NCiMgSW1wb3J0IHBhY2thZ2VzDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeShSUXVhbnRMaWIpDQpsaWJyYXJ5KHBsb3RseSkNCmBgYA0KDQojIyBTdGVwIDA6ICBJbXBvcnQgZGF0YSBhbmQgcmVmZXJlbmNlIGluZGljZXM7IHJlbW92ZSBzdXBlcmZsdW91cyB2YXJpYWJsZXMgdG8gY3JlYXRlIGRhdGFzZXRzIGZvciBhZ2VuY3kgY29ycmVjdGlvbg0KDQpgYGB7cn0NCiMgSW1wb3J0IG5lZWRlZCBpbmRpY2VzDQpzZXR3ZCgnRGF0YXNldHMvSW5kaWNlcycpDQpwYXJrX2lkcyA8LSByZWFkX2NzdignUGFyayBhbmQgQWdlbmN5IENvZGVzIEluZGV4LmNzdicpICMgRm9yIFN0ZXAgMg0KZW50cmFuY2VfY2xhc3NpZmljYXRpb25zIDwtIHJlYWRfY3N2KCcyMDE4IEVudHJhbmNlIFVzYWdlIENsYXNzaWZpY2F0aW9ucyBJbmRleC5jc3YnKSAjIEZvciBTdGVwIDMgJiA1DQpidXNfdmVoaWNsZV9tdWx0aXBsaWVycyA8LSByZWFkX2NzdignQnVzIGFuZCBWZWhpY2xlIE11bHRpcGxpZXJzIEluZGV4LmNzdicpICMgRm9yIFN0ZXAgNA0KYGBgDQoNCiMjIyBSdW4gdGhlIGZvbGxvd2luZyBpbXBvcnQgYW5kIGV4cG9ydCBvbmNlLCBwcmlvciB0byByZXF1ZXN0aW5nIFF1YWx0cmljcyBhY2N1cmFjeSBhdWRpdHMgYXQgdGhlIGVuZCBvZiB0aGUgc2Vhc29uDQoNCmBgYHtyfQ0KIyBJbXBvcnQgMjAxOCBRdWFsdHJpY3MgZGF0YXNldHMgLSBzZWUgUXVhbHRyaWNzIEluZm8gZG9jdW1lbnQgZm9yIGxvZ2luIGluZm9ybWF0aW9uDQojIE5vdGUgdGhhdCBmb3IgaW1wb3J0IGJ5IGNvZGUgYmVsb3cgdG8gd29yaywgYWxsIGRhdGFzZXRzIG11c3QgYmUgY3N2cywgc2F2ZWQgd2l0aCBvcmlnaW5hbCBRdWFsdHJpY3MgbmFtZXMgaW4gJ0RhdGFzZXRzJyBmb2xkZXIgKGkuZS4gZG93bmxvYWQgYXMgY3N2IGZyb20gUXVhbHRyaWNzIGFuZCBkbyBub3QgY2hhbmdlIHRoZSBuYW1lKQ0Kc2V0d2QoJy4nKQ0Kc2V0d2QoJ0RhdGFzZXRzL1F1YWx0cmljcycpDQpwYXJrc18xOCA8LSBsaXN0LmZpbGVzKHBhdHRlcm4gPSAnKi5jc3YnKQ0KDQptYWtlX25hbWVzIDwtIGZ1bmN0aW9uKG5hbWVfdmVjdG9yKSB7DQogDQogIG5hbWVfdmVjdG9yX2RmIDwtIGFzLmRhdGEuZnJhbWUobmFtZV92ZWN0b3IpDQogDQogbmV3X25hbWVzIDwtIG5hbWVfdmVjdG9yX2RmICU+JQ0KICAgcmVuYW1lKEFnZW5jeV9uYW1lID0gMSkgJT4lDQogICBtdXRhdGUoQWdlbmN5X25hbWUgPSBzdHJfcmVwbGFjZV9hbGwoQWdlbmN5X25hbWUsIGMoJ1xcKy0rLis/dicgPSAnJywgJ1xcKycgPSAnXycpKSkNCg0KIG5ld19uYW1lc19saXN0IDwtIGFzLmxpc3QobmV3X25hbWVzKQ0KIA0KIHJldHVybihuZXdfbmFtZXNfbGlzdCRBZ2VuY3lfbmFtZSkNCn0NCg0KbGlzdDJlbnYobWFwKHNldF9uYW1lcyhwYXJrc18xOCwgbWFrZV9uYW1lcyhwYXJrc18xOCkpLCByZWFkX2NzdiksIGVudmlyID0gLkdsb2JhbEVudikNCg0KYWxsX2FnZW5jaWVzX2xpc3QgPC0gbGlzdChBbm9rYV9Db3VudHkgPSBBbm9rYV9Db3VudHksIEJsb29taW5ndG9uID0gQmxvb21pbmd0b24sIENhcnZlcl9Db3VudHkgPSBDYXJ2ZXJfQ291bnR5LCBEYWtvdGFfQ291bnR5ID0gRGFrb3RhX0NvdW50eSwgTWlubmVhcG9saXNfUGFya19hbmRfUmVjcmVhdGlvbl9Cb2FyZCA9IE1pbm5lYXBvbGlzX1BhcmtfYW5kX1JlY3JlYXRpb25fQm9hcmQsIFJhbXNleV9Db3VudHkgPSBSYW1zZXlfQ291bnR5LCBTYWludF9QYXVsID0gU2FpbnRfUGF1bCwgU2NvdHRfQ291bnR5ID0gU2NvdHRfQ291bnR5LCBUaHJlZV9SaXZlcnNfUGFya19EaXN0cmljdCA9IFRocmVlX1JpdmVyc19QYXJrX0Rpc3RyaWN0LCBXYXNoaW5ndG9uX0NvdW50eSA9IFdhc2hpbmd0b25fQ291bnR5KQ0KDQojIFJlbW92ZSB1bm5lY2Vzc2FyeSB2YXJpYWJsZXMgdG8gY2xlYW4gZGF0YXNldHMgcHJpb3IgdG8gc2VuZGluZyB0byBhZ2VuY2llcw0KIyANCiMgdmFyaWFibGVfcGFyaW5nIDwtIGZ1bmN0aW9uKGRmKSB7DQojICAgZGYgJT4lDQojICAgICBzbGljZSgtMikgJT4lDQojICAgICBzZWxlY3QoMTg6MzMpDQojIH0NCiMgDQojIHBhcmtzX3BhcmVkIDwtIG1hcChhbGxfYWdlbmNpZXNfbGlzdCwgdmFyaWFibGVfcGFyaW5nKQ0KDQojIFdyaXRlIG91dCBkYXRhc2V0cyB3aXRob3V0IGV4dHJhIHZhcmlhYmxlcyB0byBmb2xkZXIgb24gTiBkcml2ZSwgZnJvbSB3aGljaCB0aGV5IGNhbiBiZSBzZW50IGZvciBjb3JyZWN0aW9uDQoNCiMgcGFya3NfcGFyZWQgJT4lDQojICAgbmFtZXMoLikgJT4lDQojICAgbWFwKH4gd3JpdGVfY3N2KHBhcmtzX3BhcmVkW1suXV0sIHBhc3RlMCgiRGF0YXNldHMvVG8gU2VuZCBmb3IgQWNjdXJhY3kgQXVkaXQvIiwgLiwgIi5jc3YiKSkpDQpgYGANCg0KIyMgU3RlcCAxOiAgVGlkeSBhZ2VuY2llcycgZmluYWwgZGF0YSAod2l0aCBjb3JyZWN0aW9ucyksIGJpbmQgdG9nZXRoZXINCg0KYGBge3J9DQojc2V0d2QoJy4nKQ0KI3NldHdkKCdEYXRhc2V0cy9BdWRpdGVkJykNCiNwYXJrc18xOCA8LSBsaXN0LmZpbGVzKHBhdHRlcm4gPSAnKi5jc3YnKQ0KDQphbGxfYWdlbmNpZXNfbGlzdCA8LSBsaXN0KEFub2thX0NvdW50eSwgQmxvb21pbmd0b24sIENhcnZlcl9Db3VudHksIERha290YV9Db3VudHksIE1pbm5lYXBvbGlzX1BhcmtfYW5kX1JlY3JlYXRpb25fQm9hcmQsIFJhbXNleV9Db3VudHksIFNhaW50X1BhdWwsIFNjb3R0X0NvdW50eSwgVGhyZWVfUml2ZXJzX1BhcmtfRGlzdHJpY3QsIFdhc2hpbmd0b25fQ291bnR5KQ0KDQpwYXJrX3RpZHkgPC0gZnVuY3Rpb24oZGYpIHsNCiAgZGYgJT4lDQogICAgc2xpY2UoLTE6LTIpICU+JQ0KICByZW5hbWUoQ291bnRlcl9pbml0aWFscyA9IDE4LA0KICAgICAgICAgQWdlbmN5X25hbWUgPSAxOSwNCiAgICAgICAgIFBhcmtfdHJhaWwgPSAyMCwNCiAgICAgICAgIEVudHJhbmNlID0gMjEsDQogICAgICAgICBNb250aCA9IDIyLA0KICAgICAgICAgRGF5ID0gMjMsDQogICAgICAgICBZZWFyID0gMjQsDQogICAgICAgICBUaW1lc2xvdCA9IDI1LA0KICAgICAgICAgU2NoZWR1bGVkX2NvdW50ID0gMjYsDQogICAgICAgICBTY2hlZHVsZWRfZGF0ZSA9IDI3LA0KICAgICAgICAgQmlrZXJzID0gMjgsDQogICAgICAgICBQZWRzX3NrYXRlcnMgPSAyOSwNCiAgICAgICAgIFZlaGljbGVzID0gMzAsDQogICAgICAgICBCdXNlcyA9IDMxLA0KICAgICAgICAgSG9yc2VfcmlkZXJzID0gMzIsDQogICAgICAgICBCb2F0ZXJzID0gMzMpICU+JQ0KICBzZWxlY3QoOCwgOSwgMTg6MzMpICU+JQ0KICBtdXRhdGUoU3RhcnRfdGltZSA9IGlmZWxzZShUaW1lc2xvdCAhPSAnMTA6MDAtMTI6MDAgUE0nLCBzdHJfcmVwbGFjZShUaW1lc2xvdCwgJ1xcLS4rPyAnLCAnJyksICcxMDowMEFNJykpICU+JQ0KICBzZXBhcmF0ZShTdGFydF90aW1lLCBpbnRvID0gYygnU3RhcnQnLCAnQU1fUE0nKSwgc2VwID0gLTIpICU+JQ0KICB1bml0ZShTdGFydF90aW1lLCBTdGFydCwgQU1fUE0sIHNlcCA9ICcgJykgJT4lDQogIG11dGF0ZShaZXJvID0gMCwNCiAgICAgICAgIFN0YXJ0X3RpbWVfMiA9IFN0YXJ0X3RpbWUpICU+JQ0KICB1bml0ZShTdGFydF9hbHQsIFplcm8sIFN0YXJ0X3RpbWVfMiwgc2VwID0gJycpICU+JQ0KICBtdXRhdGUoU3RhcnRfdGltZV9mbXQgPSBpZmVsc2UoU3RhcnRfdGltZSAhPSAnMTA6MDAgQU0nICYgU3RhcnRfdGltZSAhPSAnMTI6MDAgUE0nLCBTdGFydF9hbHQsIFN0YXJ0X3RpbWUpKSAlPiUNCiAgc2VsZWN0KC1TdGFydF9hbHQpDQogIA0KfQ0KDQphZ2VuY2llc190aWRpZWQgPC0gbWFwKGFsbF9hZ2VuY2llc19saXN0LCBwYXJrX3RpZHkpDQoNCmFnZW5jaWVzX2JvdW5kIDwtIGRvLmNhbGwoYmluZF9yb3dzLCBhZ2VuY2llc190aWRpZWQpDQoNCmBgYA0KDQojIyBTdGVwIDI6ICBDcmVhdGUgYSBkYXkgdHlwZSB2YXJpYWJsZSAod2Vla2VuZC9ob2xpZGF5IG9yIHdlZWtkYXkpOyBjcmVhdGUgYSB2YXJpYWJsZSB3aXRoIHRoZSB0b3RhbCBudW1iZXIgb2YgZGF5cyBmb3IgdGhhdCBkYXkgdHlwZSBpbiB0aGUgc2Vhc29uDQoNCiMjIyBNYWtlIHN1cmUsIGluIHRoaXMgc3RlcCwgdG8gY2hhbmdlIHRoZSB5ZWFyIGluIHRoZSBmaXJzdCB0d28gbGluZXMgdG8gdGhlIHBlcnRpbmVudCB5ZWFyIC0gdGhpcyBoYXMgdG8gYmUgZG9uZSBtYW51YWxseSBiZWNhdXNlIHdoaWxlIHVzdWFsbHkgdXNlIGVzdGltYXRlcyBhcmUgZG9uZSB0aGUgeWVhciBhZnRlciBzdW1tZXIgY291bnRzIGFyZSBjb21wbGV0ZSwgaXQgY291bGQgYmUgdGhhdCB0aGV5IGFyZSBkb25lIGluIHRoZSBzYW1lIHllYXIgYXMgdGhlIGNvdW50cyBpbiB5ZWFycyB0byBjb21lDQoNCmBgYHtyfQ0KIyBDYWxjdWxhdGUgZGF5cyBpbiB0aGUgc3VtbWVyIHNlYXNvbiBmb3IgdGhlIHllYXINCm1lbW9yaWFsX2xhYm9yIDwtIGFzLmRhdGEuZnJhbWUoUlF1YW50TGliOjpnZXRIb2xpZGF5TGlzdChjYWxlbmRhciA9ICdVbml0ZWRTdGF0ZXMnLCBmcm9tID0gYXNfZGF0ZSh5bWQoJzIwMTgwNTAxJykpLCB0byA9IGFzX2RhdGUoeW1kKCcyMDE4MDkzMCcpKSwgaW5jbHVkZVdlZWtlbmRzID0gRkFMU0UpKQ0KDQpzdW1tZXJfZGF5cyA8LSBtZW1vcmlhbF9sYWJvciAlPiUNCiAgcmVuYW1lKERhdGUgPSAxKSAlPiUNCiAgc2xpY2UoLTIpICU+JSAjIHJlbW92ZSBGb3VydGggb2YgSnVseQ0KICBtdXRhdGUocm93X2lkID0gcm93X251bWJlcigpKSAlPiUNCiAgbXV0YXRlKEhvbGlkYXkgPSBpZmVsc2Uocm93X2lkID09IDEsICdNZW1vcmlhbF9EYXknLCAnTGFib3JfRGF5JykpICU+JQ0KICBzZWxlY3QoLXJvd19pZCkgJT4lDQogIHNwcmVhZChIb2xpZGF5LCB2YWx1ZSA9IERhdGUpICU+JQ0KICBtdXRhdGUoTWVtb3JpYWxfRGF5X1NhdHVyZGF5ID0gYXNfZGF0ZSh5bWQoTWVtb3JpYWxfRGF5KSktIGRheXMoMikpICU+JSAjIFN1bW1lciBzZWFzb24gYmVnaW5zIHRoZSBTYXR1cmRheSBiZWZvcmUgTWVtb3JpYWwgRGF5DQogIG11dGF0ZShTZWFzb25fcGVyaW9kID0gYXMucGVyaW9kKGludGVydmFsKHN0YXJ0ID0gYXNfZGF0ZSh5bWQoTWVtb3JpYWxfRGF5X1NhdHVyZGF5KSksIGVuZCA9IGFzX2RhdGUoeW1kKExhYm9yX0RheSkpKSwgdW5pdCA9ICdkYXknKSkgJT4lDQogIHNlcGFyYXRlKFNlYXNvbl9wZXJpb2QsIGludG8gPSBjKCdTZWFzb25fZGF5cycsICJITVNfaW5fc2Vhc29uIiksIHNlcCA9ICdkJykgJT4lDQogIG11dGF0ZShIYWxmX29mX3N1bW1lciA9IChhcy5udW1lcmljKFNlYXNvbl9kYXlzKS8yKSkgJT4lDQogIG11dGF0ZShNaWRwb2ludF9vZl9zdW1tZXIgPSBhc19kYXRlKHltZChNZW1vcmlhbF9EYXlfU2F0dXJkYXkpKSArIGRheXMoSGFsZl9vZl9zdW1tZXIpKQ0KDQojIENhbGN1bGF0ZSBudW1iZXIgb2Ygd2Vla2VuZC9ob2xpZGF5IGRheXMgYW5kIG51bWJlciBvZiB3ZWVrZGF5cyBpbiBzdW1tZXIgc2Vhc29uIC0gdGhpcyANCmRheV90eXBlX2NvdW50cyA8LSBhcy5kYXRhLmZyYW1lKHNlcShhcy5EYXRlKHN1bW1lcl9kYXlzJE1lbW9yaWFsX0RheV9TYXR1cmRheSksIGFzLkRhdGUoc3VtbWVyX2RheXMkTGFib3JfRGF5KSwgImRheXMiKSkgJT4lDQogIHJlbmFtZShEYXRlID0gMSkgJT4lDQogIG11dGF0ZShIb2xpZGF5ID0gUlF1YW50TGliOjppc0hvbGlkYXkoY2FsZW5kYXIgPSAiVW5pdGVkU3RhdGVzIiwgZGF0ZXMgPSBEYXRlKSkgJT4lDQogIG11dGF0ZShEYXlfdHlwZSA9IGlmZWxzZShIb2xpZGF5ID09ICdUUlVFJywgJ1dlZWtlbmRzX2hvbGlkYXlzJywgJ1dlZWtkYXlzJykpICU+JQ0KICBncm91cF9ieShEYXlfdHlwZSkgJT4lDQogIGNvdW50KCkgJT4lDQogIHNwcmVhZChEYXlfdHlwZSwgdmFsdWUgPSBuKQ0KDQojIEFzc2lnbiB3aGljaCBoYWxmIG9mIHRoZSBzdW1tZXIgdGhlIGRheSBmYWxscyBpbiAodGhpcyBpcyB1c2VkLCBpbiBwYXJ0LCApOyBhc3NpZ24gZGF5LXR5cGUgdmFyaWFibGU7IGFzc2lnbiBudW1iZXIgb2YgZGF5cyBvZiB0aGF0IGRheS10eXBlIGluIHRoZSBzZWFzb24gKHRoaXMgbnVtYmVyIGlzIHVzZWQgYXMgYSBtdWx0aXBsaWVyIHRvIHNjYWxlIGNvdW50cyB1cCkNCnN0ZXBfSUkgPC0gYWdlbmNpZXNfYm91bmQgJT4lDQogIG11dGF0ZShZZWFyID0gaWZlbHNlKGlzLm5hKFllYXIpLCAnMjAxOCcsIFllYXIpKSAlPiUgIyBTb21lIGFnZW5jaWVzIGRvbid0IGVudGVyIHRoZSB5ZWFyOyBmaWxsIGluIGZvciB0aGVtDQogIG11dGF0ZShNb250aF91bml0ZSA9IE1vbnRoLA0KICAgICAgICAgRGF5X3VuaXRlID0gRGF5LA0KICAgICAgICAgWWVhcl91bml0ZSA9IFllYXIpICU+JQ0KICB1bml0ZShEYXRlLCBNb250aF91bml0ZSwgRGF5X3VuaXRlLCBZZWFyX3VuaXRlLCBzZXAgPSAiICIpICU+JQ0KICBtdXRhdGUoRGF0ZSA9IGFzX2RhdGUobWR5KERhdGUpKSkgJT4lDQogIG11dGF0ZShIYWxmX29mX3N1bW1lciA9IGlmZWxzZShEYXRlIDw9IHN1bW1lcl9kYXlzJE1pZHBvaW50X29mX3N1bW1lciwgMSwgMikpICU+JSANCiAgdW5pdGUoRGF0ZV95ZWFyLCBNb250aCwgRGF5LCBZZWFyLCBzZXAgPSAnLScpICU+JQ0KICBtdXRhdGUoRGF0ZV9vYmplY3QgPSBhc19kYXRlKG1keShEYXRlX3llYXIpKSkgJT4lDQogIG11dGF0ZShIb2xpZGF5ID0gUlF1YW50TGliOjppc0hvbGlkYXkoY2FsZW5kYXIgPSAiVW5pdGVkU3RhdGVzIiwgZGF0ZXMgPSBEYXRlX29iamVjdCkpICU+JQ0KICBtdXRhdGUoRGF5X3R5cGUgPSBpZmVsc2UoSG9saWRheSA9PSAnVFJVRScsICdXZWVrZW5kX2hvbGlkYXknLCAnV2Vla2RheScpKSAlPiUNCiAgbXV0YXRlKFRvdGFsX2RheXNfYnlfdHlwZSA9IGlmZWxzZShEYXlfdHlwZSA9PSAnV2Vla2VuZF9ob2xpZGF5JywgZGF5X3R5cGVfY291bnRzJFdlZWtlbmRzX2hvbGlkYXlzLCBkYXlfdHlwZV9jb3VudHMkV2Vla2RheXMpKSAlPiUNCiAgc2VsZWN0KC1Ib2xpZGF5KSAlPiUNCiAgbXV0YXRlKFBhcmtfdHJhaWwgPSBpZmVsc2UoUGFya190cmFpbCA9PSAnTWlubmVzb3RhIFJpdmVyIEdyZWVud2F5JywgJ01pbm5lc290YSBSaXZlciBHcmVlbndheSBSZWdpb25hbCBUcmFpbCcsIFBhcmtfdHJhaWwpKSAlPiUNCiAgbXV0YXRlKEFnZW5jeV9uYW1lID0gaWZlbHNlKFBhcmtfdHJhaWwgPT0gJ0JhdHRsZSBDcmVlayBSZWdpb25hbCBQYXJrJywgJ1JhbXNleSBDb3VudHknLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKFBhcmtfdHJhaWwgPT0gJ01pc3Npc3NpcHBpIEdvcmdlIFJlZ2lvbmFsIFBhcmsnLCAnTWlubmVhcG9saXMgUGFyayBhbmQgUmVjcmVhdGlvbiBCb2FyZCcsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKFBhcmtfdHJhaWwgPT0gJ0hpZGRlbiBGYWxscy1Dcm9zYnkgRmFybSBSZWdpb25hbCBQYXJrJywgJ1NhaW50IFBhdWwnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoUGFya190cmFpbCA9PSAnU2NvdHQgQ291bnR5IFJlZ2lvbmFsIFRyYWlsJywgJ1Njb3R0IENvdW50eScsIEFnZW5jeV9uYW1lKSkpKSkgJT4lICMgVGhlc2UgYWdlbmN5IHNlbGVjdGlvbiBmb3IgdGhlc2UgdHJhaWxzIGZyb20gdGhlIFF1YWx0cmljcyBmb3JtIGFyZSBpbmNvcnJlY3QNCiAgbXV0YXRlKFN0YXJ0X3RpbWUgPSBhcy5jaGFyYWN0ZXIoU3RhcnRfdGltZV9mbXQpKSAlPiUNCiAgc2VsZWN0KC1TdGFydF90aW1lX2ZtdCkNCg0KYGBgDQoNCiMjIFN0ZXAgMmE6ICBBZGQgbnVtZXJpYyBQYXJrIElkZW50aWZpZXIgdG8gZGF0YXNldCAodXNlZCBpbiBzdGVwIDMgdG8gYWRkIHBhcmsgZW50cmFuY2UgdXNhZ2UgY2xhc3NpZmljYXRpb25zKQ0KDQpgYGB7cn0NCg0Kc3RlcF9JSWEgPC0gbGVmdF9qb2luKHN0ZXBfSUksIHBhcmtfaWRzLCBieSA9IGMoJ1BhcmtfdHJhaWwnID0gJ1BhcmtfbmFtZScsICdBZ2VuY3lfbmFtZScgPSAnQWdlbmN5X25hbWUnKSkNCg0KIyBFbnN1cmUgYWxsIGRhdGEgam9pbmVkIHByb3Blcmx5DQoNCnN0ZXBfSUlhX3Vuam9pbmVkIDwtIHN0ZXBfSUlhICU+JQ0KICBmaWx0ZXIoaXMubmEoUGFya19pZCkgfCBpcy5uYShBZ2VuY3lfaWQpKQ0KDQpgYGANCg0KIyMgU3RlcCAyYSBjaGVjazogIENoZWNrIHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIGluIHRoZSBzdGVwX0lJYSBkYXRhc2V0LiAgSWYgdGhlIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgaW5jcmVhc2VkLCBzb21lIG9mIHRoZSBjb3VudHMgaGF2ZSBiZWVuIGR1cGxpY2F0ZWQgdXBvbiBqb2luLiAgVXNlIHRoZSBmb2xsb3dpbmcgY29kZSB0byBkaXNjZXJuIHdoeSBhbmQgaG93IHRvIGFtZWxpb3JhdGUgdGhlIGZhdXggcGFzLg0KDQpgYGB7cn0NCg0KIyBXaGljaCBhZ2VuY3kgb3IgYWdlbmNpZXMgaGF2ZSBkdXBsaWNhdGVkIGNvdW50cz8NCg0KIyBzdGVwX0lJX2FnZW5jeV9jb3VudHMgPC0gc3RlcF9JSSAlPiUNCiMgICBncm91cF9ieShBZ2VuY3lfbmFtZSkgJT4lDQojICAgY291bnQoKQ0KIyANCiMgc3RlcF9JSWFfYWdlbmN5X2NvdW50cyA8LSBzdGVwX0lJYSAlPiUNCiMgICBncm91cF9ieShBZ2VuY3lfbmFtZSkgJT4lDQojICAgY291bnQoKQ0KIyANCiMgc3RlcF9JSWFfY29tcGFyZV9hZ2VuY3lfY291bnRzIDwtIGZ1bGxfam9pbihzdGVwX0lJX2FnZW5jeV9jb3VudHMsIHN0ZXBfSUlhX2FnZW5jeV9jb3VudHMsIGJ5ID0gYygnQWdlbmN5X25hbWUnKSkNCiMgDQojICMgSW4gdGhpcyBjYXNlLCBvbmx5IEFub2thLiAgV2hpY2ggQW5va2EgcGFya3Mgb3IgdHJhaWxzIHdlcmUgZHVwbGljYXRlZD8NCiMgDQojIHN0ZXBfSUlfcGFya19jb3VudHMgPC0gc3RlcF9JSSAlPiUNCiMgICBmaWx0ZXIoQWdlbmN5X25hbWUgPT0gJ0Fub2thIENvdW50eScpICU+JQ0KIyAgIGdyb3VwX2J5KFBhcmtfdHJhaWwpICU+JQ0KIyAgIGNvdW50KCkNCiMgDQojIHN0ZXBfSUlhX3BhcmtfY291bnRzIDwtIHN0ZXBfSUlhICU+JQ0KIyAgIGZpbHRlcihBZ2VuY3lfbmFtZSA9PSAnQW5va2EgQ291bnR5JykgJT4lDQojICAgZ3JvdXBfYnkoUGFya190cmFpbCkgJT4lDQojICAgY291bnQoKQ0KIyANCiMgc3RlcF9JSWFfY29tcGFyZV9wYXJrX2NvdW50cyA8LSBmdWxsX2pvaW4oc3RlcF9JSV9wYXJrX2NvdW50cywgc3RlcF9JSWFfcGFya19jb3VudHMsIGJ5ID0gYygnUGFya190cmFpbCcpKQ0KIyANCiMgc3RlcF9JSWFfY29tcGFyZV9jb3VudHMgJT4lDQojICAgZmlsdGVyKG4ueCAhPSBuLnkpDQojICMgSW4gdGhpcyBjYXNlLCBSdW0gUml2ZXIgQ2VudHJhbCBSZWdpb25hbCBQYXJrIGlzIHJlc3BvbnNpYmxlIGZvciB0aGUgZHVwbGljYXRpb24uICBSdW0gUml2ZXIgQ1JQIHdhcyBnaXZlbiB0d28gcGFyayBhdHRyaWJ1dGlvbnMgZm9yIEFub2thIENvdW50eSwgYnkgbWlzdGFrZS4gIFJlbW92ZSB0aGUgJzEwOScgYXR0cmlidXRpb24gZnJvbSB0aGUgaW5kZXguDQojIA0KIyBzdGVwX0lJYV91bmpvaW5lZCA8LSBzdGVwX0lJYSAlPiUNCiMgICBmaWx0ZXIoaXMubmEoUGFya19pZCkgfCBpcy5uYShBZ2VuY3lfaWQpKSAlPiUNCiMgICBzZWxlY3QoUGFya190cmFpbCwgQWdlbmN5X25hbWUpICU+JQ0KIyAgIHVuaXF1ZSgpDQoNCmBgYA0KDQojIyBTdGVwIDM6ICBBZGQgUGFyayBFbnRyYW5jZSBVc2FnZSBDbGFzc2lmaWNhdGlvbnMgKEhpZ2gsIE1lZGl1bSwgTG93KQ0KDQpgYGB7cn0NCiMgTm90ZSB0aGF0IFRyb3V0IEJyb29rJ3MgUlQgRW50cmFuY2UgNCB3YXMgZml4ZWQgbWFudWFsbHk7IGlmIHRoZSAyMDE4IEVudHJhbmNlIFVzYWdlIENsYXNzaWZpY2F0aW9ucyBJbmRleCBpcyByZS1ydW4gaW4gdGhlIGNvbnZlcnNpb24gd29ya2Jvb2ssIHRoaXMgd2lsbCBhZ2FpbiBuZWVkIHRvIGJlIGNoYW5nZWQgbWFudWFsbHkgKG9yIHNjcmlwdGVkKSBhY2NvcmRpbmcgdG8gdGhlIGRvY3VtZW50ICdUcm91dCBCcm9vayBSVCBFbnRybmFjZSA0IDIwMTggRGVzaWduYXRpb25zJw0KZW50cmFuY2VfY2xhc3Nfam9pbiA8LSBlbnRyYW5jZV9jbGFzc2lmaWNhdGlvbnMgJT4lDQogIG11dGF0ZShFbnRyYW5jZSA9IGFzLmNoYXJhY3RlcihFbnRyYW5jZSkpICU+JQ0KICBtdXRhdGUoU3RhcnRfdGltZSA9IGZvcm1hdChzdHJwdGltZShzdWJzdHIoYXMuUE9TSVhjdChzcHJpbnRmKCIlMDQuZiIsIGFzLm51bWVyaWMoU3RhcnRfdGltZSkpLCBmb3JtYXQ9IiVIJU0iKSwgMTIsIDE2KSwgJyVIOiVNJyksICclSTolTSAlcCcpKQ0KDQpzdGVwX0lJSSA8LSBsZWZ0X2pvaW4oc3RlcF9JSWEsIGVudHJhbmNlX2NsYXNzX2pvaW4sIGJ5ID0gYygnUGFya19pZCcgPSAnUGFya190cmFpbCcsICdFbnRyYW5jZScsICdTdGFydF90aW1lJywgJ0RheV90eXBlJykpDQoNCmBgYA0KDQoNCmBgYHtyfQ0KIyBDaGVjayB0aGF0IHRoZXJlJ3Mgbm8gbWlzc2luZyBkYXRhIGZvciBjbGFzcyBlbnRyYW5jZXMsIGFzIGFsbCBlbnRyYW5jZXMgc2hvdWxkIHJlY2VpdmUgYSBjbGFzc2lmaWNhdGlvbi4gIElmIHRoZXJlIGFyZSBvYnNlcnZhdGlvbnMgbWlzc2luZyBhIGNsYXNzaWZpY2F0aW9uLCB0aGlzIGlzIG1vc3QgbGlrZWx5IGR1ZSB0byBpbmNvcnJlY3QgZGF0YSBlbnRyeSAoY29udmVyc2lvbiBmcm9tIHNhbXBsZSBjbGFzcyB3b3JrYm9vayB0byBpbmRleCBoYXMgYmVlbiBhdWRpdGVkIG11bHRpcGxlIHdheXMgYW5kIGRlLWJ1Z2dlZDsgdGhlcmVmb3JlIGl0IGlzIHNob3duIHRvIGhhdmUgY2FwdHVyZWQgMTAwJSBvZiB0aGUgZGF0YSBpbiB0aGUgc2FtcGxlIGNsYXNzIHdvcmtib29rcykuICBDaGVjayBpZiB0cmFpbCBlbnRyYW5jZSBleGlzdHM7IGlmIHRyYWlsIGVudHJhbmNlIGRvZXMgbm90IGV4aXN0IGluIFNhbXBsZSBDbGFzcyBXb3JrYm9vaywgY2hlY2sgZW50cmFuY2UgZGVzY3JpcHRpb25zLiAgSWYgaXQgZG9lc24ndCBleGlzdCBpbiBlbnRyYW5jZSBkZXNjcmlwdGlvbnMsIG1vc3QgbGlrZWx5IHRoaXMgaXMgZGF0YSBlcnJvci4gIENvbnRhY3QgSUEgY29vcmRpbmF0b3IgZm9yIGNvcnJlY3Rpb25zLg0KDQptaXNzaW5nX2NsYXNzX2Fzc2lnbm1lbnQgPC0gc3RlcF9JSUkgJT4lDQogIGZpbHRlcihpcy5uYShVc2FnZV9jbGFzcykpICU+JQ0KICBncm91cF9ieShQYXJrX3RyYWlsLCBBZ2VuY3lfbmFtZSwgU3RhcnRfdGltZSwgRW50cmFuY2UpICU+JQ0KICBjb3VudCgpDQoNCnN0ZXBfSUlJX2R1cHMgPC0gc3RlcF9JSUkgJT4lDQogIGdyb3VwX2J5KFBhcmtfaWQsIERhdGUsIFN0YXJ0X3RpbWUsIEVudHJhbmNlLCBSZXNwb25zZUlkKSAlPiUNCiAgY291bnQoKSAlPiUNCiAgZmlsdGVyKG4gPiAxKQ0KDQpzdGVwX0lJSV9kdXBzX2Z1bGwgPC0gaW5uZXJfam9pbihzdGVwX0lJSSwgc3RlcF9JSUlfZHVwcywgYnkgPSBjKCdQYXJrX2lkJywgJ0RhdGUnLCAnU3RhcnRfdGltZScsICdFbnRyYW5jZScsICdSZXNwb25zZUlkJykpDQoNCmBgYA0KDQojIyBTdGVwIDQ6ICBBZGQgcGVyc29ucyBwZXIgdmVoaWNsZSBhbmQgcGVyc29ucyBwZXIgYnVzIG11bHRpcGxpZXJzLCBkZWZpbmVkIGJ5IHRoZSAyMDE2IFZpc2l0b3IgU3R1ZHkgYW5kIDE5OTgtMTk5OSBWaXNpdG9yIFN0dWR5LCByZXNwZWN0aXZlbHkNCg0KYGBge3J9DQoNCnN0ZXBfSVYgPC0gbGVmdF9qb2luKHN0ZXBfSUlJLCBidXNfdmVoaWNsZV9tdWx0aXBsaWVycywgYnkgPSBjKCdQYXJrX2lkJyA9ICdQYXJrJykpDQoNCmBgYA0KDQojIyMgQ29kZSBiZWxvdyBjaGVja3Mgd2hpY2ggcGFya3MgZGlkIG5vdCBoYXZlIG11bHRpcGxpZXINCg0KYGBge3J9DQpzdGVwX0lWX3Vuam9pbmVkIDwtIHN0ZXBfSVYgJT4lDQogICBmaWx0ZXIoaXMubmEocHBiX211bHRpcGxpZXIpIHwgaXMubmEocHB2X211bHRpcGxpZXIpKSAlPiUNCiAgIHNlbGVjdChQYXJrX2lkKSAlPiUNCiAgIHVuaXF1ZSgpDQoNCiMjIFRoZXNlIHBhcmtzIGFyZSBuZXcgYW5kIHRoZXJlZm9yZSBkb24ndCBoYXZlIGFzc2lnbmVkIG11bHRpcGxpZXJzOyBob3dldmVyIHRoZXkgaGF2ZSB6ZXJvIHZlaGljbGVzIG9yIGJ1c2VzIGVudGVyaW5nIHRoZSBwYXJrDQoNCiMgQ2hlY2sgZnVsbCBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIG1pc3NpbmcgZGF0YQ0Kc3RlcF9JVl91bmpvaW5lZF9mdWxsIDwtIHN0ZXBfSVYgJT4lDQogIGZpbHRlcihpcy5uYShwcGJfbXVsdGlwbGllcikgfCBpcy5uYShwcHZfbXVsdGlwbGllcikpDQoNCiMgQ2hlY2sgbnVtYmVyIG9mIGJ1c2VzIGFuZCB2ZWhpY2xlcyB0aGF0IGNhbWUgaW50byB0aGUgbmV3IHBhcmtzDQpzdGVwX0lWX3Vuam9pbmVkX2J2IDwtIHN0ZXBfSVYgJT4lDQogIGZpbHRlcihQYXJrX2lkID09IDEzMyB8IFBhcmtfaWQgPT0gMTQwKSAlPiUNCiAgc2VsZWN0KFZlaGljbGVzLCBCdXNlcykgJT4lDQogIGZpbHRlcihWZWhpY2xlcyAhPSAwIHwgQnVzZXMgIT0gMCkNCg0KDQojIyBDaGFuZ2UgbWlzc2luZyBwcHYgYW5kIHBwYiBtdWx0aXBsaWVycyB0byAxDQpzdGVwX0lWX3RpZHkgPC0gc3RlcF9JViAlPiUNCiAgbXV0YXRlKHBwYl9tdWx0aXBsaWVyID0gaWZlbHNlKGlzLm5hKHBwYl9tdWx0aXBsaWVyKSwgMSwgcHBiX211bHRpcGxpZXIpLA0KICAgICAgICAgcHB2X211bHRpcGxpZXIgPSBpZmVsc2UoaXMubmEocHB2X211bHRpcGxpZXIpLCAxLCBwcHZfbXVsdGlwbGllcikpDQpgYGANCg0KDQojIyBTdGVwIDU6ICBDYWxjdWxhdGUgZXhwYW5zaW9uIGZhY3RvcnMgJiBhdHRyaWJ1dGUgdG8gcGFya3MNCg0KYGBge3J9DQoNCmV4cGFuc2lvbl9mYWN0b3JzIDwtIGVudHJhbmNlX2NsYXNzaWZpY2F0aW9ucyAlPiUNCiAgZ3JvdXBfYnkoUGFya190cmFpbCwgVXNhZ2VfY2xhc3MsIERheV90eXBlKSAlPiUNCiAgY291bnQoKSAlPiUNCiAgcmVuYW1lKEV4cGFuc2lvbl9mYWN0b3IgPSBuKQ0KDQojIEpvaW4gbXVsdGlwbGllcnMgdG8gcGFya3MgdG8gdXNlIHRvIGNhbGN1bGF0ZSB0b3RhbCBudW1iZXIgb2YgdmlzaXRvcnMgaW4gc2FtcGxlDQoNCnN0ZXBfViA8LSBsZWZ0X2pvaW4oc3RlcF9JVl90aWR5LCBleHBhbnNpb25fZmFjdG9ycywgYnkgPSBjKCdQYXJrX2lkJyA9ICdQYXJrX3RyYWlsJywgJ1VzYWdlX2NsYXNzJyA9ICdVc2FnZV9jbGFzcycsICdEYXlfdHlwZScgPSAnRGF5X3R5cGUnKSkNCg0KDQojIENoZWNrIHRoYXQgb2JzZXJ2YXRpb25zIGFyZSBub3QgbWlzc2luZyBhbiBleHBhbnNpb24gZmFjdG9yIGFzc2lnbm1lbnQuICBObyBvYnNlcnZhdGlvbiBzaG91bGQgYmUgbWlzc2luZyBhbiBhc3NpZ25tZW50IChpLmUuIHRoaXMgY29kZSBzaG91bGQgcmV0dXJuIGEgdGliYmxlIHdpdGggMCBvYnNlcnZhdGlvbnMpLiAgSWYgb2JzZXJ2YXRpb25zIGFyZSBtaXNzaW5nIGFuIGFzc2lnbm1lbnQsIGZvbGxvdyB0aGUgc2FtZSBzdGVwcyBvdXRsaW5lZCBpbiB0aGUgbWlzc2luZyBhc3NpZ25tZW50IGNoZWNrIG9mIFN0ZXAgMyBvZiB0aGlzIHNjcmlwdC4NCnN0ZXBfVl91bmpvaW5lZCA8LSBzdGVwX1YgJT4lDQogIGZpbHRlcihpcy5uYShFeHBhbnNpb25fZmFjdG9yKSkNCg0KIyBGb3IgcHVycG9zZXMgb2YgcHJlbGltaW5hcnkgY2hlY2sgb2YgbnVtYmVycywgdXNlIG9ubHkgdGhvc2UgY291bnRzIHRoYXQgaGF2ZSBhbiBleHBhbnNpb24gZmFjdG9yIGFzc2lnbmVkICh1bi1hc3NpZ25lZCBmYWN0b3JzIG1heSBiZSBiZWNhdXNlIHBhcmtzIGFyZSBuZXcgb3IgYmVjYXVzZSBvZiBpbmNvcnJlY3QgZGF0YSBlbnRyeSkNCnN0ZXBfVl9ub3RfbmEgPC0gc3RlcF9WICU+JQ0KICBmaWx0ZXIoIWlzLm5hKEV4cGFuc2lvbl9mYWN0b3IpKQ0KDQpgYGANCg0KIyMgU3RlcCA2OiAgQ29tcHV0ZSBleHBhbmRlZCB0b3RhbCBvZiB2aXNpdG9ycyAodG90YWwgZm9yIGVudGlyZSBzZWFzb24sIGJhc2VkIG9uIHNhbXBsZSkgYXQgZW50cmFuY2UgYW5kIGJ5IGRhdGUgYW5kIHRpbWUgb2YgZGF5DQoNCmBgYHtyfQ0KIyBDb21wdXRlIGV4cGFuZGVkIHRvdGFsIG9mIHZpc2l0b3JzICh0b3RhbCBmb3IgZW50aXJlIHNlYXNvbiwgYmFzZWQgb24gc2FtcGxlKQ0KDQpzdGVwX1ZJIDwtIHN0ZXBfVl9ub3RfbmEgJT4lDQogIG11dGF0ZShFeHBhbmRlZF90b3RhbCA9IChwcHZfbXVsdGlwbGllciphcy5udW1lcmljKFZlaGljbGVzKSArIGFzLm51bWVyaWMoQmlrZXJzKSArIGFzLm51bWVyaWMoUGVkc19za2F0ZXJzKSArIHBwYl9tdWx0aXBsaWVyKmFzLm51bWVyaWMoQnVzZXMpICsgYXMubnVtZXJpYyhIb3JzZV9yaWRlcnMpICsgYXMubnVtZXJpYyhCb2F0ZXJzKSkgKiBhcy5udW1lcmljKFRvdGFsX2RheXNfYnlfdHlwZSkgKiBhcy5udW1lcmljKEV4cGFuc2lvbl9mYWN0b3IpKQ0KDQpgYGANCg0KIyMgU3RlcCA3OiAgQWRkIHRvIHByZXZpb3VzIHRocmVlIHllYXJzIG9mIGRhdGENCg0KYGBge3J9DQpzZXR3ZCgnRGF0YXNldHMvMjAxNC0yMDE3JykNCngyMDE0XzIwMTcgPC0gcmVhZF9jc3YoJzIwMTQtMjAxNyBEYXRhLmNzdicpDQoNCngyMDE1XzIwMTcgPC0geDIwMTRfMjAxNyAlPiUNCiAgbXV0YXRlKExlYXN0X3JlY2VudF95ZWFyID0gbWluKFllYXIpKSAlPiUNCiAgZmlsdGVyKFllYXIgIT0gTGVhc3RfcmVjZW50X3llYXIpICU+JSAjIEZpbHRlciBkb3duIHRvIHRocmVlIG1vc3QgcmVjZW50IHllYXJzIG9mIGRhdGENCiAgc2VsZWN0KC1MZWFzdF9yZWNlbnRfeWVhcikgJT4lDQogIHNlcGFyYXRlKGdvb2RkYXRlLCBpbnRvID0gYygnTW9udGgnLCAnRGF5JyksIHNlcCA9IDEpICU+JQ0KICBtdXRhdGUoWWVhcl9mb3JfZGF0ZSA9IFllYXIpICU+JQ0KICB1bml0ZShEYXRlLCBNb250aCwgRGF5LCBZZWFyX2Zvcl9kYXRlLCBzZXAgPSAnLScpICU+JQ0KICBtdXRhdGUoRGF0ZSA9IGFzX2RhdGUobWR5KERhdGUpKSkgJT4lDQogIHJlbmFtZShBZ2VuY3lfaWQgPSBhZ2VuY3ksDQogICAgICAgICBQYXJrX2lkID0gUGFyaywNCiAgICAgICAgIEJpa2VycyA9IEJpa2VyLA0KICAgICAgICAgUGVkc19za2F0ZXJzID0gUGVkc2thdCwNCiAgICAgICAgIEJvYXRlcnMgPSBCb2F0LA0KICAgICAgICAgVmVoaWNsZXMgPSBWZWhpY2xlLA0KICAgICAgICAgQnVzZXMgPSBCdXMsDQogICAgICAgICBIb3JzZV9yaWRlcnMgPSBIb3JzZSwNCiAgICAgICAgIFVzYWdlX2NsYXNzID0gc2FtY2xhc3MsDQogICAgICAgICBUb3RhbF92aXNpdG9ycyA9IHRvdGFsYWxsLA0KICAgICAgICAgRGF5X3R5cGUgPSBkYXl0eXBlLA0KICAgICAgICAgU3RhcnRfdGltZSA9IG9yZ3N0YXJ0LA0KICAgICAgICAgRXhwYW5zaW9uX2ZhY3RvciA9IGV4cGZhY3QsDQogICAgICAgICBwcHZfbXVsdGlwbGllciA9IG5ldzIwMDhwcHYsDQogICAgICAgICBwcGJfbXVsdGlwbGllciA9IG5ld3BwYiwNCiAgICAgICAgIFRvdGFsX2RheXNfYnlfdHlwZSA9IGBkYXkjc2AsDQogICAgICAgICBIYWxmX29mX3N1bW1lciA9IGhhbGYsDQogICAgICAgICBFeHBhbmRlZF90b3RhbCA9IHRvdGFsc3VtKSAlPiUNCiAgc2VsZWN0KC1jYXJwZW9wLCAtYnVzcGVvcCkgJT4lDQogIG11dGF0ZShTdGFydF90aW1lID0gc3RyX3JlcGxhY2UoU3RhcnRfdGltZSwgIjEwMDAwIiwgIjEwMDAiKSwNCiAgICAgICAgIFN0YXJ0X3RpbWUgPSBhcy5udW1lcmljKFN0YXJ0X3RpbWUpKSAlPiUNCiAgbXV0YXRlKFVzYWdlX2NsYXNzID0gaWZlbHNlKFVzYWdlX2NsYXNzID09IDEsICdIaWdoJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShVc2FnZV9jbGFzcyA9PSAyLCAnTWVkaXVtJywgJ0xvdycpKSkgJT4lDQogIG11dGF0ZShTdGFydF90aW1lID0gZm9ybWF0KHN0cnB0aW1lKHN1YnN0cihhcy5QT1NJWGN0KHNwcmludGYoIiUwNC5mIiwgYXMubnVtZXJpYyhTdGFydF90aW1lKSksIGZvcm1hdD0iJUglTSIpLCAxMiwgMTYpLCAnJUg6JU0nKSwgJyVJOiVNICVwJykpICU+JQ0KICBtdXRhdGUoRGF5X3R5cGUgPSBpZmVsc2UoRGF5X3R5cGUgPT0gMSwgJ1dlZWtlbmRfaG9saWRheScsICdXZWVrZGF5JykpDQoNCg0KeDIwMTggPC0gc3RlcF9WSSAlPiUNCiAgbXV0YXRlKFllYXIgPSAyMDE4KSAlPiUNCiAgbXV0YXRlKEVudHJhbmNlID0gYXMubnVtZXJpYyhFbnRyYW5jZSksDQogICAgICAgICBCaWtlcnMgPSBhcy5udW1lcmljKEJpa2VycyksDQogICAgICAgICBQZWRzX3NrYXRlcnMgPSBhcy5udW1lcmljKFBlZHNfc2thdGVycyksDQogICAgICAgICBWZWhpY2xlcyA9IGFzLm51bWVyaWMoVmVoaWNsZXMpLA0KICAgICAgICAgQnVzZXMgPSBhcy5udW1lcmljKEJ1c2VzKSwNCiAgICAgICAgIEJvYXRlcnMgPSBhcy5udW1lcmljKEJvYXRlcnMpLA0KICAgICAgICAgSG9yc2VfcmlkZXJzID0gYXMubnVtZXJpYyhIb3JzZV9yaWRlcnMpKQ0KICANCg0KeDIwMTVfMjAxOCA8LSBiaW5kX3Jvd3MoeDIwMTVfMjAxNywgeDIwMTgpDQoNCmBgYA0KDQojIyBTdGVwIDg6ICBDb21wdXRlIGF2ZXJhZ2VzIGJ5IFBhcmssIFRyYWlsIEVudHJhbmNlL1RpbWVzbG90IENsYXNzaWZpY2F0aW9uLCBhbmQgRGF5IHR5cGUgKFdlZWtlbmQvaG9saWRheSBvciBXZWVrZGF5KQ0KDQpgYGB7cn0NCngyMDE1XzIwMThfYXZnIDwtIHgyMDE1XzIwMTggJT4lDQogIG11dGF0ZShQYXJrX2lkID0gaWZlbHNlKFBhcmtfaWQgPT0gMTIxLCAxMTYsIFBhcmtfaWQpKSAlPiUgIyBOb3Qgc3VyZSB3aHksIGJ1dCBpbiBwYXN0IHllYXJzIFNXIFJUIHdhcyBjb2RlZCBhcyAxMjEuICBJbiBhbGwgZG9jdW1lbnRhdGlvbiwgaG93ZXZlciwgaXQncyBjb2RlZCBhcyAxMTYNCiAgdW5pcXVlKCkgJT4lDQogIGdyb3VwX2J5KFBhcmtfaWQsIFVzYWdlX2NsYXNzLCBEYXlfdHlwZSkgJT4lDQogIG11dGF0ZShBdmVyYWdlX2V4cGFuZGVkX3RvdGFsID0gbWVhbihFeHBhbmRlZF90b3RhbCkpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIHNlbGVjdChQYXJrX2lkLCBVc2FnZV9jbGFzcywgRGF5X3R5cGUsIEF2ZXJhZ2VfZXhwYW5kZWRfdG90YWwpICU+JQ0KICB1bmlxdWUoKSAlPiUNCiAgc2VsZWN0KC1Vc2FnZV9jbGFzcywgLURheV90eXBlKSAlPiUNCiAgZ3JvdXBfYnkoUGFya19pZCkgJT4lDQogIG11dGF0ZShFc3RpbWF0ZV8xOCA9IHN1bShBdmVyYWdlX2V4cGFuZGVkX3RvdGFsKSkgJT4lDQogIHNlbGVjdCgtQXZlcmFnZV9leHBhbmRlZF90b3RhbCkgJT4lDQogIHVuaXF1ZSgpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIG11dGF0ZShQYXJrX2lkID0gYXMubnVtZXJpYyhQYXJrX2lkKSkgJT4lDQogIGFycmFuZ2UoUGFya19pZCkNCg0KYGBgDQoNCiMjIFN0ZXAgOGE6ICBDb21wYXJlIHRvIHByZXZpb3VzIHllYXIncyBlc3RpbWF0ZXM7IG5vdGUgdGhhdCBsYXJnZSBkaWZmZXJlbmNlcyBkbyBub3QgaW5oZXJlbnRseSBtZWFuIGluY29ycmVjdCBlc3RpbWF0ZXMNCg0KYGBge3J9DQpzZXR3ZCgnRGF0YXNldHMvMjAxNC0yMDE3JykNCmVzdGltYXRlc18yMDE3IDwtIHJlYWRfY3N2KCcyMDE3IFVzZSBFc3RpbWF0ZXMuY3N2JykNCg0KeDIwMTdfdGlkeSA8LSBlc3RpbWF0ZXNfMjAxNyAlPiUNCiAgcmVuYW1lKFBhcmtfdHJhaWwgPSBgUGFyayBvciBUcmFpbGAsDQogICAgICAgICBFc3RpbWF0ZV8xNyA9IGBUb3RhbCBieSBjbGFzcyBhbmQgZGF5IHR5cGVgKSAlPiUNCiAgc2VwYXJhdGUoUGFya190cmFpbCwgaW50byA9IGMoJ1BhcmtfaWQnLCAnVHJhaWxfbmFtZScpLCBzZXAgPSAnPScpICU+JQ0KICBtdXRhdGUoUGFya19pZCA9IHRyaW13cyhQYXJrX2lkKSwNCiAgICAgICAgIFRyYWlsX25hbWUgPSB0cmltd3MoVHJhaWxfbmFtZSkpICU+JQ0KICBtdXRhdGUoUGFya19pZCA9IGlmZWxzZShQYXJrX2lkID09IDEyMSwgMTE2LCBQYXJrX2lkKSkgJT4lDQogIG11dGF0ZShQYXJrX2lkID0gYXMubnVtZXJpYyhQYXJrX2lkKSkNCg0KZXN0aW1hdGVzXzE3XzE4IDwtIGxlZnRfam9pbih4MjAxNV8yMDE4X2F2ZywgeDIwMTdfdGlkeSwgYnkgPSBjKCdQYXJrX2lkJykpDQoNCmVzdGltYXRlX2NvbXBhcmlzb24gPC0gZXN0aW1hdGVzXzE3XzE4ICU+JQ0KICBtdXRhdGUoRXN0aW1hdGVfMTcgPSBpZmVsc2UoaXMubmEoRXN0aW1hdGVfMTcpLCAwLCBFc3RpbWF0ZV8xNykpICU+JQ0KICBtdXRhdGUoRXN0aW1hdGVfZ3Jvc3NfZGlmZmVyZW5jZSA9IEVzdGltYXRlXzE4LUVzdGltYXRlXzE3KSAlPiUNCiAgbXV0YXRlKFBlcmNlbnRfb2ZfMTggPSAoRXN0aW1hdGVfMTgvc3VtKEVzdGltYXRlXzE4KSkqMTAwLA0KICAgICAgICAgUGVyY2VudF9vZl8xNyA9IChFc3RpbWF0ZV8xNy9zdW0oRXN0aW1hdGVfMTcpKSoxMDAsDQogICAgICAgICBFc3RpbWF0ZV9wZXJjZW50X29mX3dob2xlX2RpZmZlcmVuY2UgPSBQZXJjZW50X29mXzE4LVBlcmNlbnRfb2ZfMTcpICU+JQ0KICBtdXRhdGUoUGVyY2VudF9kaWZmZXJlbmNlX2Zyb21fMTdfMThfYnlfdHJhaWwgPSAxMDAtKChFc3RpbWF0ZV8xOC9Fc3RpbWF0ZV8xNykqMTAwKSkNCg0KIyBQZXJjZW50IGRpZmZlcmVuY2UgaW4gVHJhaWwgb3IgUGFyaydzIFNoYXJlIG9mIEFsbCBSZWdpb25hbCBWaXNpdHMgYmV0d2VlbiAyMDE3IGFuZCAyMDE4DQpnZ3Bsb3QoZXN0aW1hdGVfY29tcGFyaXNvbiwgYWVzKFRyYWlsX25hbWUsIEVzdGltYXRlX3BlcmNlbnRfb2Zfd2hvbGVfZGlmZmVyZW5jZSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsNCiAgY29vcmRfZmxpcCgpICsNCiAgbGFicyh0aXRsZSA9ICJQZXJjZW50IERpZmZlcmVuY2UgaW4gVHJhaWwgb3IgUGFyaydzIFNoYXJlIG9mIGFsbCBSZWdpb25hbCBWaXNpdHMgZnJvbSAyMDE3IHRvIDIwMTgiKQ0KDQojIFBlcmNlbnQgRGlmZmVyZW5jZSBmcm9tIDIwMTcgRXN0aW1hdGUgdG8gMjAxOCBFc3RpbWF0ZQ0KZ2dwbG90KGVzdGltYXRlX2NvbXBhcmlzb24sIGFlcyhUcmFpbF9uYW1lLCBQZXJjZW50X2RpZmZlcmVuY2VfZnJvbV8xN18xOF9ieV90cmFpbCkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsNCiAgY29vcmRfZmxpcCgpICsNCiAgbGFicyh0aXRsZSA9ICJQZXJjZW50IERpZmZlcmVuY2UgaW4gVHJhaWwgb3IgUGFyaydzIDIwMTcgRXN0aW1hdGUgdG8gSXRzIDIwMTggRXN0aW1hdGUiKQ0KDQojIEdyb3NzIERpZmZlcmVuY2UgZnJvbSAyMDE3IEVzdGltYXRlIHRvIDIwMTggRXN0aW1hdGUNCmdncGxvdChlc3RpbWF0ZV9jb21wYXJpc29uLCBhZXMoVHJhaWxfbmFtZSwgRXN0aW1hdGVfZ3Jvc3NfZGlmZmVyZW5jZSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsNCiAgY29vcmRfZmxpcCgpICsNCiAgbGFicyh0aXRsZSA9ICJHcm9zcyBEaWZmZXJlbmNlIGZyb20gMjAxNyBFc3RpbWF0ZSB0byAyMDE4IEVzdGltYXRlIikNCg0KDQplc3RpbWF0ZV9jb21wYXJpc29uICU+JQ0KICBnYXRoZXIoRXN0aW1hdGVfMTcsIEVzdGltYXRlXzE4LCBrZXkgPSAnRXN0aW1hdGVfeWVhcicsIHZhbHVlID0gJ0VzdGltYXRlJykgJT4lDQogIGdncGxvdChhZXMoVHJhaWxfbmFtZSwgRXN0aW1hdGUsIGZpbGwgPSBFc3RpbWF0ZV95ZWFyKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gJ2lkZW50aXR5JywgcG9zaXRpb24gPSAnZG9kZ2UnKSArIGNvb3JkX2ZsaXAoKQ0KDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiMjIEZvbGxvd2luZyBpcyBhbiBleHBsb3JhdGlvbiBvZiBvdXRsaWVycw0KDQpgYGB7cn0NCiMgUGxvdCAyMDE4IGNvdW50cyBhcyBzdHJpcGxvdDsgdXNlIHBsb3RseSB0byBtb3VzZSBvdmVyIHNwZWNpZmljIG9ic2VydmF0aW9ucyB0aGF0IGFwcGVhciB0byBiZSBvdXRsaWVycw0Kc3RyaXBjaGFydF8xOCA8LSB4MjAxOCAlPiUNCiAgbXV0YXRlKFRvdGFsX3Zpc2l0b3JzID0gKHBwdl9tdWx0aXBsaWVyKmFzLm51bWVyaWMoVmVoaWNsZXMpICsgYXMubnVtZXJpYyhCaWtlcnMpICsgYXMubnVtZXJpYyhQZWRzX3NrYXRlcnMpICsgcHBiX211bHRpcGxpZXIqYXMubnVtZXJpYyhCdXNlcykgKyBhcy5udW1lcmljKEhvcnNlX3JpZGVycykgKyBhcy5udW1lcmljKEJvYXRlcnMpKSkgJT4lDQogIGdncGxvdChhZXMoUGFya19pZCwgVG90YWxfdmlzaXRvcnMsIGNvbG9yID0gQWdlbmN5X2lkKSkgKw0KICBnZW9tX3BvaW50KGFscGhhID0gMC42LCBzaXplID0gMikgKw0KICBjb29yZF9mbGlwKCkgKw0KICBzY2FsZV9jb2xvcl9kaXN0aWxsZXIocGFsZXR0ZSA9ICJSZFB1IikgKw0KICBsYWJzKHRpdGxlID0gJzIwMTggUmF3IENvdW50cycsDQogICAgICAgeSA9ICdWaXNpdG9ycyBDb3VudGVkJykgKw0KICB0aGVtZShheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gOCkpDQoNCmdncGxvdGx5KHN0cmlwY2hhcnRfMTgpDQoNCmBgYA0KDQpgYGB7cn0NCiMgUGxvdCAyMDE4IGNvdW50cyBhcyBib3hwbG90OyB1c2UgcGxvdGx5IHRvIHpvb20gJiBtb3VzZSBvdmVyIHNwZWNpZmljIG9ic2VydmF0aW9ucyB0aGF0IGFwcGVhciB0byBiZSBvdXRsaWVycw0KYm94cGxvdF8xOCA8LSB4MjAxOCAlPiUNCiAgbXV0YXRlKFRvdGFsX3Zpc2l0b3JzID0gKHBwdl9tdWx0aXBsaWVyKmFzLm51bWVyaWMoVmVoaWNsZXMpICsgYXMubnVtZXJpYyhCaWtlcnMpICsgYXMubnVtZXJpYyhQZWRzX3NrYXRlcnMpICsgcHBiX211bHRpcGxpZXIqYXMubnVtZXJpYyhCdXNlcykgKyBhcy5udW1lcmljKEhvcnNlX3JpZGVycykgKyBhcy5udW1lcmljKEJvYXRlcnMpKSkgJT4lDQogIGdncGxvdChhZXMoUGFya190cmFpbCwgVG90YWxfdmlzaXRvcnMpKSArDQogIGdlb21fYm94cGxvdChhbHBoYSA9IDAuNiwgc2l6ZSA9IC41KSArDQogIGNvb3JkX2ZsaXAoKSArDQogIGxhYnModGl0bGUgPSAnMjAxOCAnLA0KICAgICAgIHkgPSAnQ291bnRlZCBWaXNpdG9ycycpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgpKQ0KDQpnZ3Bsb3RseShib3hwbG90XzE4KQ0KDQpgYGANCg0KYGBge3J9DQojIFBsb3QgMjAxOCBkaXN0cmlidXRpb24gY3VydmUgb2YgU2lsdmVyd29vZCBTUkYgYnkgZGF5IHR5cGUNCiB4MjAxOCAlPiUNCiAgZmlsdGVyKFBhcmtfdHJhaWwgPT0gJ1NpbHZlcndvb2QgU3BlY2lhbCBSZWNyZWF0aW9uIEZlYXR1cmUnKSAlPiUNCiAgbXV0YXRlKFRvdGFsX3Zpc2l0b3JzID0gKHBwdl9tdWx0aXBsaWVyKmFzLm51bWVyaWMoVmVoaWNsZXMpICsgYXMubnVtZXJpYyhCaWtlcnMpICsgYXMubnVtZXJpYyhQZWRzX3NrYXRlcnMpICsgcHBiX211bHRpcGxpZXIqYXMubnVtZXJpYyhCdXNlcykgKyBhcy5udW1lcmljKEhvcnNlX3JpZGVycykgKyBhcy5udW1lcmljKEJvYXRlcnMpKSkgJT4lDQogIGdncGxvdChhZXMoVG90YWxfdmlzaXRvcnMsIGNvbG9yID0gRGF5X3R5cGUsIGZpbGwgPSBEYXlfdHlwZSkpICsNCiAgZ2VvbV9kZW5zaXR5KGFscGhhID0gMC4xKSArDQogIGxhYnModGl0bGUgPSAnMjAxOCAnLA0KICAgICAgIHggPSAnQ291bnRlZCB2aXNpdG9ycycsDQogICAgICAgeSA9ICdPYnNlcnZhdGlvbnMnKSArDQogIHRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSA4KSkNCg0KeDIwMTggJT4lDQogIGZpbHRlcihQYXJrX3RyYWlsID09ICdTaWx2ZXJ3b29kIFNwZWNpYWwgUmVjcmVhdGlvbiBGZWF0dXJlJykgJT4lDQogIG11dGF0ZShUb3RhbF92aXNpdG9ycyA9IChwcHZfbXVsdGlwbGllciphcy5udW1lcmljKFZlaGljbGVzKSArIGFzLm51bWVyaWMoQmlrZXJzKSArIGFzLm51bWVyaWMoUGVkc19za2F0ZXJzKSArIHBwYl9tdWx0aXBsaWVyKmFzLm51bWVyaWMoQnVzZXMpICsgYXMubnVtZXJpYyhIb3JzZV9yaWRlcnMpICsgYXMubnVtZXJpYyhCb2F0ZXJzKSkpICU+JQ0KICBnZ3Bsb3QoYWVzKFRvdGFsX3Zpc2l0b3JzLCBmaWxsID0gRGF5X3R5cGUpKSArDQogIGdlb21faGlzdG9ncmFtKHNpemUgPSAxKSArDQogIGxhYnModGl0bGUgPSAnMjAxOCAnLA0KICAgICAgIHggPSAnQ291bnRlZCB2aXNpdG9ycycsDQogICAgICAgeSA9ICdPYnNlcnZhdGlvbnMnKSArDQogIHRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSA4KSkNCg0KIyBQbG90IDIwMTggY291bnRzIGZvciBTaWx2ZXJ3b29kIFNSRiBhcyBkZW5zaXR5IGN1cnZlLCBubyBkYXkgdHlwZQ0KIHgyMDE4ICU+JQ0KICBmaWx0ZXIoUGFya190cmFpbCA9PSAnU2lsdmVyd29vZCBTcGVjaWFsIFJlY3JlYXRpb24gRmVhdHVyZScpICU+JQ0KICBtdXRhdGUoVG90YWxfdmlzaXRvcnMgPSAocHB2X211bHRpcGxpZXIqYXMubnVtZXJpYyhWZWhpY2xlcykgKyBhcy5udW1lcmljKEJpa2VycykgKyBhcy5udW1lcmljKFBlZHNfc2thdGVycykgKyBwcGJfbXVsdGlwbGllciphcy5udW1lcmljKEJ1c2VzKSArIGFzLm51bWVyaWMoSG9yc2VfcmlkZXJzKSArIGFzLm51bWVyaWMoQm9hdGVycykpKSAlPiUNCiAgZ2dwbG90KGFlcyhUb3RhbF92aXNpdG9ycykpICsNCiAgZ2VvbV9kZW5zaXR5KCkgKw0KICBsYWJzKHRpdGxlID0gJzIwMTggJywNCiAgICAgICB4ID0gJ0NvdW50ZWQgdmlzaXRvcnMnLA0KICAgICAgIHkgPSAnT2JzZXJ2YXRpb25zJykgKw0KICB0aGVtZShheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gOCkpDQoNCiAjIFBsb3QgYWxsIHRyYWlscy9wYXJrcyBkZW5zaXR5IGN1cnZlcyANCngyMDE4ICU+JQ0KICBtdXRhdGUoVG90YWxfdmlzaXRvcnMgPSAocHB2X211bHRpcGxpZXIqYXMubnVtZXJpYyhWZWhpY2xlcykgKyBhcy5udW1lcmljKEJpa2VycykgKyBhcy5udW1lcmljKFBlZHNfc2thdGVycykgKyBwcGJfbXVsdGlwbGllciphcy5udW1lcmljKEJ1c2VzKSArIGFzLm51bWVyaWMoSG9yc2VfcmlkZXJzKSArIGFzLm51bWVyaWMoQm9hdGVycykpKSAlPiUNCiAgZ2dwbG90KGFlcyhUb3RhbF92aXNpdG9ycykpICsNCiAgZ2VvbV9kZW5zaXR5KCkgKw0KICBsYWJzKHRpdGxlID0gJzIwMTggJywNCiAgICAgICB4ID0gJ0NvdW50ZWQgdmlzaXRvcnMnLA0KICAgICAgIHkgPSAnT2JzZXJ2YXRpb25zJykgKw0KICB0aGVtZShheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gOCkpICsNCiAgZmFjZXRfd3JhcCh+UGFya190cmFpbCkNCg0KZ2dzYXZlKCcyMDE4IERpc3RyaWJ1dGlvbiBDdXJ2ZXMgZm9yIENvdW50ZWQgVmlzaXRvcnMuanBnJywgd2lkdGggPSA1MCwgaGVpZ2h0ID0gMzAsIGxpbWl0c2l6ZSA9IEZBTFNFKQ0KYGBgDQoNCg0KDQo=